package online.model.check;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import online.model.ModelUtil;
import online.model.UniModel;
import online.model.session.ActionParameter;
import online.model.session.SessionUser;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import core.config.Factory;
import core.exception.LogicalException;
import core.exception.PhysicalException;
import core.exception.ThrowableUtil;
import core.util.ArrayUtil;
import core.util.MojiUtil;

/**
 * 入力チェッククラス
 *
 * @author Tadashi Nakayama
 * @version 1.0.0
 */
public final class InputCheckImpl implements InputCheck {
	/** ログ出力オブジェクト */
	private static final Logger LOGGER = LogManager.getLogger(InputCheckImpl.class);

	/** トップメッセージ */
	private final List<String> message = new ArrayList<>();
	/** トップメッセージステータス */
	private final List<String> status = new ArrayList<>();
	/** 入力チェック保持リスト */
	private final List<Check> chk = new ArrayList<>();
	/** 参照パラメタ */
	private final Map<String, String[]> ref = new HashMap<>();
	/** 変換チェック保持 */
	private final Map<String, ItemConvert<?>> conv = new HashMap<>();

	/** 返却値 */
	private String rc = null;
	/** データモデル */
	private UniModel um = null;
	/** アクションパラメタ */
	private ActionParameter ap = null;
	/** セションユーザ情報 */
	private SessionUser su = null;
	/** 削除位置 */
	private int[] deleted = new int[0];
	/** エラー時操作 */
	private ItemOperation ope = null;

	/**
	 * 汎用モデル設定
	 *
	 * @param model 汎用モデル
	 */
	@Override
	public void setUniModel(final UniModel model) {
		this.um = model;
	}

	/**
	 * アクションパラメタ設定
	 *
	 * @param param アクションパラメタ
	 */
	@Override
	public void setActionParameter(final ActionParameter param) {
		this.ap = param;
	}

	/**
	 * セションユーザ情報設定
	 *
	 * @param user セションユーザ情報
	 */
	@Override
	public void setSessionUser(final SessionUser user) {
		this.su = user;
	}

	/**
	 * 削除位置設定
	 *
	 * @param loc 削除位置
	 */
	@Override
	public void setDeletedLocation(final int[] loc) {
		this.deleted = new int[0];
		if (loc != null) {
			this.deleted = loc.clone();
		}
	}

	/**
	 * 復帰値設定
	 *
	 * @param val 復帰値
	 */
	@Override
	public void setReturn(final String val) {
		this.rc = val;
	}

	/**
	 * トップメッセージID設定
	 *
	 * @param msg メッセージID
	 * @param sts メッセージ可変部
	 */
	@Override
	public void setMessage(final String msg, final String sts) {
		this.message.clear();
		this.status.clear();
		addMessage(msg, sts);
	}

	/**
	 * トップメッセージID追加
	 *
	 * @param msg メッセージID
	 * @param sts メッセージ可変部
	 */
	@Override
	public void addMessage(final String msg, final String sts) {
		this.message.add(msg);
		this.status.add(sts);
	}

	/**
	 * 入力チェック実行
	 *
	 * @param io チェッククラス
	 */
	@Override
	public void onError(final ItemOperation io) {
		this.ope = io;
	}

	/**
	 * 入力チェック設定
	 *
	 * @param item 項目名
	 * @param ic チェッククラス
	 * @param others その他項目名
	 */
	@Override
	public void add(final String item, final ItemCheck ic, final String... others) {
		setInputChecker(ic);

		String[] items = others;
		if (item != null) {
			items = ArrayUtil.extend(new String[]{item}, others);
		}
		this.chk.add(new Check(ic, items));

		if (ItemConvert.class.isInstance(ic)) {
			for (final String itm : items) {
				if (this.conv.get(itm) == null) {
					this.conv.put(itm, ItemConvert.class.cast(ic));
				}
			}
		}
	}

	/**
	 * 入力チェッカー設定
	 *
	 * @param icb 入力チェック
	 */
	private void setInputChecker(final Object icb) {
		if (ItemCheckBase.class.isInstance(icb)) {
			ItemCheckBase.class.cast(icb).setActionParameter(this.ap);
			ItemCheckBase.class.cast(icb).setSessionUser(this.su);
			ItemCheckBase.class.cast(icb).setUniModel(this.um);
			ItemCheckBase.class.cast(icb).setReferenceMap(this.ref);
			ItemCheckBase.class.cast(icb).setMessageList(this.message, this.status);
			ItemCheckBase.class.cast(icb).setDelFlg(this.deleted);
		}
	}

	/**
	 * 入力チェックリスト削除
	 */
	@Override
	public void clear() {
		this.message.clear();
		this.status.clear();
		this.chk.clear();
		this.ref.clear();
		this.conv.clear();
		this.deleted = new int[0];
		this.ope = null;
	}

	/**
	 * チェック処理
	 */
	@Override
	public void check() {
		checkItem();
		clear();
	}

	/**
	 * チェック後入力値取込処理
	 */
	@Override
	public void populate() {
		checkItem();
		setAllRefParameter();
		clear();
	}

	/**
	 * チェック処理
	 */
	private void checkItem() {
		// チェックリスト数分処理
		Check error = null;
		for (final Check c : this.chk) {
			if (c != null && c.getCheck() != null) {
				try {
					ItemCheck ic = c.getCheck();
					ic.check(c.getItems());
				} catch (NoMoreCheckException ex) {
					throwException(c);
				} catch (KeepCheckException ex) {
					if (error == null) {
						error = c;
					}
				} catch (ItemCheckException ex) {
					ThrowableUtil.error(ex);
					throw new PhysicalException(ex);
				} catch (LogicalException | PhysicalException ex) {
					throw ex;
				}
			}
		}

		if (error != null) {
			throwException(error);
		}
	}

	/**
	 * 参照したキーに対応する値をすべて設定する。
	 */
	private void setAllRefParameter() {
		for (final Entry<String, String[]> ent : this.ref.entrySet()) {
			ItemConvert<?> ic = getConverter(ent.getKey());
			putTo(this.um, ent.getKey(), ic.convert(correct(ent.getValue())));
		}
	}

	/**
	 * 文字化け修正
	 *
	 * @param val 文字列配列
	 * @return 文字化け修正後配列
	 */
	private String[] correct(final String[] val) {
		String[] ret = new String[val.length];
		for (int i = 0; i < val.length; i++) {
			ret[i] = MojiUtil.correctGarbled(val[i], ItemCheckBase.getMapping());
		}
		return ret;
	}

	/**
	 * モデル設定
	 *
	 * @param <T> ジェネリックス
	 * @param model 汎用モデル
	 * @param key 設定キー
	 * @param val 設定値
	 */
	private static <T> void putTo(final UniModel model, final String key, final T[] val) {
		if (val.length == 1) {
			Method m = Factory.getMethod(
					model.getClass(), "setValue", String.class, val.getClass().getComponentType());
			Factory.invoke(model, m, key, val[0]);
		} else {
			Method m = Factory.getMethod(
					model.getClass(), "setValue", String.class, val.getClass());
			Factory.invoke(model, m, key, val);
		}
	}

	/**
	 * 項目コンバーター取得
	 *
	 * @param key キー
	 * @return 項目コンバーター
	 */
	private ItemConvert<?> getConverter(final String key) {
		ItemConvert<?> ic = this.conv.get(key);
		if (ic == null) {
			ic = new DefaultConverter();
		}
		return ic;
	}

	/**
	 * 例外処理
	 *
	 * @param check チェッククラス
	 */
	private void throwException(final Check check) {
		if (LOGGER.isDebugEnabled()) {
			LOGGER.debug(check.getCheck().getClass() + ":" + Arrays.toString(check.getItems()));
		}

		if (this.ope != null) {
			setInputChecker(this.ope);
			this.ope.operate();
		}

		for (int i = 0; i < this.message.size(); i++) {
			this.um.addValue(ModelUtil.TAG_MESSAGE, this.message.get(i));
			this.um.addValue(ModelUtil.TAG_STATUS, this.status.get(i) + ModelUtil.TAG_SEPARATOR);
		}
		throw LogicalException.create(this.rc, getMessage(this.um), null);
	}

	/**
	 * メッセージ存在確認
	 *
	 * @param uf 汎用フォーム
	 * @return 存在した場合 true を返す。
	 */
	private String getMessage(final UniModel uf) {
		final String msg = ModelUtil.TAG_SEPARATOR + ModelUtil.TAG_MESSAGE;
		for (final String key : uf.keySet()) {
			if (key.endsWith(msg)) {
				return uf.getString(key);
			}
		}
		return uf.getString(ModelUtil.TAG_MESSAGE);
	}

	/**
	 * デフォルトコンバーター
	 *
	 * @author Tadashi Nakayama
	 */
	private static final class DefaultConverter implements ItemConvert<String> {

		/**
		 * コンストラクタ
		 */
		public DefaultConverter() {
			super();
		}

		/**
		 * @see online.model.check.ItemConvert#convert(java.lang.String...)
		 */
		@Override
		public String[] convert(final String... val) {
			return val;
		}
	}

	/**
	 * チェック保持クラス
	 *
	 * @author Tadashi Nakayama
	 * @version 1.0.0
	 */
	private static final class Check {
		/** チェック */
		private final ItemCheck check;
		/** メッセージパラメタ */
		private final String[] itm;

		/**
		 * コンストラクタ
		 *
		 * @param chk チェック
		 * @param items 項目名
		 */
		public Check(final ItemCheck chk, final String[] items) {
			this.check = chk;
			this.itm = ArrayUtil.copyOf(items);
		}

		/**
		 * @return the check
		 */
		public ItemCheck getCheck() {
			return this.check;
		}

		/**
		 * @return the itm
		 */
		public String[] getItems() {
			return ArrayUtil.copyOf(this.itm);
		}
	}
}
